#ifndef VEC3_H
#define VEC3_H

#include "Math.h"

class Vec3
{
public:

	struct MemberStruct
	{
		float x,y,z;
	};
	union
	{
		struct MemberStruct m;
		float t[3];
	};

	Vec3(void)			{m.x = 0.0f; m.y = 0.0f; m.z = 0.0f; }
	Vec3(const Vec3 & V)		{m.x = V.m.x;  m.y = V.m.y;  m.z = V.m.z;}
	Vec3(float Xi, float Yi, float Zi)	{m.x = Xi;    m.y = Yi;    m.z = Zi;}


	inline float X() const {return m.x;}
	inline float Y() const {return m.y;}
	inline float Z() const {return m.z;}

	inline void Set(float X, float Y, float Z) {m.x = X;    m.y = Y;    m.z = Z;}
	inline void Reset()			{m.x = 0.0f; m.y = 0.0f; m.z = 0.0f;}

	inline void Reverse(void)                  {m.x *= -1.0f; m.y *= -1.0f; m.z *= -1.0f;}

	inline float Magnitude(void) const		{return(Math::sqrtf(m.x*m.x + m.y*m.y + m.z*m.z));}
	inline float Dot(const Vec3 & V) const	{return(m.x*V.m.x + m.y*V.m.y + m.z*V.m.z);}

	inline Vec3 Clone(void)			{return Vec3(m.x, m.y, m.z);}

	inline bool IsUnit() const {return (Magnitude() == 1.0f);}
	inline bool IsNull() const {return (m.x == 0.0f && m.y == 0.0f && m.z == 0.0f);}

	inline bool IsPerpendicularTo(const Vec3 & V) const {return (Dot(V) == 0.0f);}

	inline void     operator =  (const Vec3 & V){m.x = V.m.x; m.y = V.m.y; m.z = V.m.z;}

	inline bool     operator == (const Vec3 & V) const {return(m.x == V.m.x && m.y == V.m.y && m.z == V.m.z);}
	inline bool     operator != (const Vec3 & V) const {return(m.x != V.m.x || m.y != V.m.y || m.z != V.m.z);}

	inline float    operator [] (int I) const {if (I == 0){return(m.x);} if (I == 1){return(m.y);} if (I == 2){return(m.z);} return(0);}

	inline Vec3   operator +  (const Vec3 & V) const {return(Vec3(m.x + V.m.x, m.y + V.m.y, m.z + V.m.z));}
	inline Vec3   operator -  (const Vec3 & V) const {return(Vec3(m.x - V.m.x, m.y - V.m.y, m.z - V.m.z));}
	inline Vec3   operator *  (const Vec3 & V) const {return(Vec3(m.x * V.m.x, m.y * V.m.y, m.z * V.m.z));}
	inline Vec3   operator /  (const Vec3 & V) const {return(Vec3(m.x / V.m.x, m.y / V.m.y, m.z / V.m.z));}

	inline Vec3   operator *  (float F)  const {return(Vec3(m.x * F,   m.y * F,   m.z * F));}
	inline Vec3   operator /  (float F)  const {return(Vec3(m.x / F,   m.y / F,   m.z / F));}

	inline void     operator += (const Vec3 & V) {m.x += V.m.x; m.y += V.m.y; m.z += V.m.z;}
	inline void     operator -= (const Vec3 & V) {m.x -= V.m.x; m.y -= V.m.y; m.z -= V.m.z;}
	inline void     operator *= (const Vec3 & V) {m.x *= V.m.x; m.y *= V.m.y; m.z *= V.m.z;}
	inline void     operator /= (const Vec3 & V) {m.x /= V.m.x; m.y /= V.m.y; m.z /= V.m.z;}

	inline void     operator *= (float F)   {m.x *= F;   m.y *= F; m.z *= F;}
	inline void     operator /= (float F)   {m.x /= F,   m.y /= F, m.z /= F;}
	
	inline Vec3   operator -  () const {return(Vec3(-m.x, -m.y, -m.z));}

	inline Vec3 & operator ++ (void) {m.x++; m.y++; m.z++; return(*this);}
	inline Vec3 & operator -- (void) {m.x--; m.y--; m.z--; return(*this);}
	inline Vec3   operator ++ (int)  {Vec3 Temp = *this; m.x++; m.y++; m.z++; return(Temp);}
	inline Vec3   operator -- (int)  {Vec3 Temp = *this; m.x--; m.y--; m.z--; return(Temp);}

	inline Vec3 Cross(const Vec3 & V)
	{
		return Vec3(
			m.y * V.m.z - m.z * V.m.y,
			m.x * V.m.z - m.z * V.m.x,
			m.x * V.m.y - m.y * V.m.x);
	}

	inline float Distance(const Vec3 & V)
	{
		return (Math::sqrtf(Math::powf(m.x - V.m.x, 2) + Math::powf(m.y - V.m.y, 2) + Math::powf(m.z - V.m.z, 2)));
	}

	inline bool IsNearlyEqualsTo (const Vec3 & V) const
	{
		float f = 10000;

		Vec3 a = (*this);
		Vec3 b = V;

		a *= f;
		b *= f;

		a.m.x = Math::floor(a.m.x);
		a.m.y = Math::floor(a.m.y);
		a.m.z = Math::floor(a.m.z);

		b.m.x = Math::floor(b.m.x);
		b.m.y = Math::floor(b.m.y);
		b.m.z = Math::floor(b.m.z);

		return(a.m.x == b.m.x && a.m.y == b.m.y && a.m.z == b.m.z);
	}

	inline void Normalize(void)
	{
		if(IsNull())return;

		float tM = Magnitude();
		m.x /= tM;	m.y /= tM;	m.z /= tM;
	}

	inline Vec3 NormalizeVector(void) const
	{
		if(IsNull())return *this;

		float tM = Magnitude();
		return(Vec3(m.x / tM, m.y / tM, m.z / tM));
	}

	inline void Truncate(float lenght)
	{
		Normalize();
		SelfScale(lenght);
	}

	inline void LookAt(const Vec3 & v)
	{
		Vec3 delta = v;
		delta -= (*this);
		delta.Normalize();
		*this = delta;
	}

	inline void SelfScale(float f)
	{
		m.x *= f; m.y *= f; m.z *= f;
	}

	inline void SelfScale(float X, float Y, float Z)
	{
		m.x *= X; m.y *= Y; m.z *= Z;
	}

	inline void SelfDivide(float f)
	{
		m.x /= f; m.y /= f; m.z /= f;
	}

	inline void SelfDivide(float X, float Y, float Z)
	{
		m.x /= X; m.y /= Y; m.z /= Z;
	}

	inline void SelfTranslate(float X, float Y, float Z)
	{
		m.x += X; m.y += Y; m.z += Z;
	}

	inline void SelfRotateX(float Angle)
	{
		float Sin = Math::sinf(Angle);
		float Cos = Math::cosf(Angle);
		float NewY = m.y*Cos - m.z*Sin;
		float NewZ = m.y*Sin + m.z*Cos;
		m.y = NewY;
		m.z = NewZ;
	}

	inline void SelfRotateY(float Angle)
	{
		float Sin = Math::sinf(Angle);
		float Cos = Math::cosf(Angle);
		float NewX = m.x*Cos + m.z*Sin;
		float NewZ = -m.x*Sin + m.z*Cos;
		m.x = NewX;
		m.z = NewZ;
	}

	inline void SelfRotateZ(float Angle)
	{
		float Sin = Math::sinf(Angle);
		float Cos = Math::cosf(Angle);
		float NewX = m.x*Cos - m.y*Sin;
		float NewY = m.x*Sin + m.y*Cos;
		m.x = NewX;
		m.y = NewY;
	}

	inline Vec3 Scale(float f) const
	{
		return(Vec3(m.x * f, m.y * f, m.z * f));
	}


	inline Vec3 Scale(float X, float Y, float Z) const
	{
		return(Vec3(m.x * X, m.y * Y, m.z * Z));
	}

	inline Vec3 Divide(float f)
	{
		return(Vec3(m.x / f, m.y / f, m.z / f));
	}


	inline Vec3 Divide(float X, float Y, float Z)
	{
		return(Vec3(m.x / X, m.y / Y, m.z / Z));
	}

	inline Vec3 Translate(float X, float Y, float Z)
	{
		return(Vec3(m.x + X, m.y + Y, m.z + Z));
	}

	inline Vec3 RotateX(float aRadAngle)
	{
		float Sin = Math::sinf(aRadAngle);
		float Cos = Math::cosf(aRadAngle);

		return(Vec3(m.x, m.y*Cos - m.z*Sin, m.y*Sin + m.z*Cos));
	}

	inline Vec3 RotateY(float aRadAngle)
	{
		float Sin = Math::sinf(aRadAngle);
		float Cos = Math::cosf(aRadAngle);

		return(Vec3(m.x*Cos + m.z*Sin, m.y, -m.x*Sin + m.z*Cos));
	}

	inline Vec3 RotateZ(float aRadAngle)
	{
		float Sin = Math::sinf(aRadAngle);
		float Cos = Math::cosf(aRadAngle);

		return(Vec3(m.x*Cos - m.y*Sin, m.x*Sin + m.y*Cos, m.z));
	}

	inline Vec3 Projection(const Vec3 & V)
	{
		return ( V * (Dot(V) / Math::powf(Magnitude(), 2)));
	}


	float AngleBetween(const Vec3 & V) const;
	Vec3 Noise(float amplitude);

	~Vec3(){}


// 	friend ostream& operator << (ostream& o, Vec3& v)
// 	{
// 		o << "X=" << v.x << " Y=" << v.m.y << " Z=" << v.m.z << endl;
// 		return o;
// 	}


};


const static Vec3 AxisX(1, 0, 0);
const static Vec3 AxisY(0, 1, 0);
const static Vec3 AxisZ(0, 0, 1);

#endif